package de.fhkl.thatsstroke;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import de.fhkl.thatsstroke.StrokeService.StrokeBinder;
import de.fhkl.thatsstroke.customview.AnimatedTextView;
import de.fhkl.thatsstroke.customview.ItemView;
import de.fhkl.thatsstroke.database.Video;
import de.fhkl.thatsstroke.database.VideoDataSource;

/**
 * Mailbox for Videos
 */
public class Inbox extends Activity
{
	public static final int VIEWED = 3;
	public static final int LOADED = 2;
	public static final int LOADING = 1;
	public static final int NOT_LOADED = 0;
	public static final int DELETING = -1;
	public static final int FADE_OUT = -2;

	private ListView mListView;
	private AnimatedTextView mRecordingStartsText;

	/** Connection to the database video information is stored in */
	private VideoDataSource mDataSource;

	/** Adapter to populate the list view for videos */
	private StrokeItemAdapter mAdapter;

	/** BroadcastReceiver for broadcast intents from the service */
	private BroadcastReceiver mReceiver;

	/**
	 * ServiceConnection to retrieve a reference for a save way to call methods
	 * from the service
	 */
	private ServiceConnection mConnection;

	/** Reference to the underlying service */
	private StrokeService mService;

	/** Indicates if the service is currently bound to this activity */
	private boolean mIsServiceBound;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_inbox);

		// Database connection, load all stored Videos
		mDataSource = new VideoDataSource(this);
		mDataSource.open();

		// Register ListViewItemAdapter with ListView
		mListView = (ListView) findViewById(R.id.video_list_view);
		mAdapter = new StrokeItemAdapter();
		mListView.setAdapter(mAdapter);
		mListView.setOnItemClickListener(mAdapter);

		mRecordingStartsText = (AnimatedTextView) findViewById(R.id.incoming_video_text);
		mRecordingStartsText.setVisibility(View.GONE);

		// In most cases this won't do anything. Service should be already
		// running. Let's try to start it just in case it doesn't.
		Intent intent = new Intent(getApplicationContext(), StrokeService.class);
		getApplicationContext().startService(intent);

		mConnection = new StrokeServiceConnection();
		mReceiver = new StrokeBroadcastReceiver();

		mIsServiceBound = false;
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		super.onCreateOptionsMenu(menu);
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.activity_inbox, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch (item.getItemId())
		{
			case R.id.close_app:
				AlertDialog.Builder builder = new AlertDialog.Builder(Inbox.this);
				builder.setMessage(R.string.confirm_close);
				builder.setPositiveButton(R.string.close_app, new DialogInterface.OnClickListener()
				{
					@Override
					public void onClick(DialogInterface dialog, int which)
					{
						// Shut down application

						// Perform logout
						mService.logout();

						// Unbind service
						Inbox.this.unbindService(mConnection);

						// make sure onStop() does not try to unbind the service
						// again
						mIsServiceBound = false;

						// Stop service
						getApplicationContext().stopService(new Intent(Inbox.this, StrokeService.class));

						// signals the activity to finish. on pause, on stop and
						// on destroy will get called after this.
						finish();
					}
				});
				builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener()
				{
					@Override
					public void onClick(DialogInterface dialog, int which)
					{
						dialog.dismiss();
					}
				});
				builder.create().show();
			break;
		}

		return true;
	}

	@Override
	protected void onStart()
	{
		super.onStart();

		this.bindService(new Intent(this, StrokeService.class), mConnection, 0);

		IntentFilter filter = new IntentFilter();
		filter.addAction(Broadcast.NEW_VIDEO);
		filter.addAction(Broadcast.VIDEO_TRANSMISSION_PROGRESS_ID);
		filter.addAction(Broadcast.TRANSMISSION_COMPLETED_ID);
		filter.addAction(Broadcast.ACCEPT_RATING_ID);
		filter.addAction(Broadcast.RECORDING_STARTS);
		filter.addAction(Broadcast.VIDEO_ASSIGNED_ID);
		filter.addAction(Broadcast.UPDATE_VIEW);
		this.registerReceiver(mReceiver, filter);
	}

	@Override
	protected void onResume()
	{
		super.onResume();

		mDataSource.open();
		mAdapter.setList(mDataSource.getAllVideos());

		mRecordingStartsText.setVisibility(View.GONE);
	}

	@Override
	protected void onPause()
	{
		mDataSource.close();

		super.onPause();
	}

	@Override
	protected void onStop()
	{
		if (mIsServiceBound)
		{
			this.unbindService(mConnection);
		}

		this.unregisterReceiver(mReceiver);

		super.onStop();
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data)
	{
		super.onActivityResult(requestCode, resultCode, data);

		if (resultCode == RESULT_CANCELED)
		{
			Toast.makeText(this, R.string.already_rated, Toast.LENGTH_LONG).show();
		}
	}

	/**
	 * Safe connection to the underlying service over a returned weak reference.
	 * Like many callbacks from the system, the methods on this class are called
	 * from the main thread of your process.
	 */
	public class StrokeServiceConnection implements ServiceConnection
	{
		@Override
		public void onServiceConnected(ComponentName name, IBinder binder)
		{
			if (!mIsServiceBound)
			{
				mService = ((StrokeBinder) binder).getService();
				// removes videos older than thirty minutes from the database and
				// SD card
				mService.cleanUpVideos();
				mIsServiceBound = true;
			}

			// Displays the information from all videos currently in the
			// database on the listView
			mAdapter.setList(mDataSource.getAllVideos());

			Log.i("StrokeServiceConnection", "Service " + mService);
		}

		@Override
		public void onServiceDisconnected(ComponentName name)
		{
			mIsServiceBound = false;
		}
	}

	/**
	 * Broadcast receiver to receive and react to broadcast messages from the
	 * underlying service.
	 */
	public class StrokeBroadcastReceiver extends BroadcastReceiver
	{
		@Override
		public void onReceive(Context context, Intent intent)
		{
			String action = intent.getAction();
			Bundle extras = intent.getExtras();

			if (action.equals(Broadcast.VIDEO_TRANSMISSION_PROGRESS_ID))
			{
				UUID id = (UUID) extras.get("de.fhkl.thatsstroke.VideoId");
				int progress = (int) extras.getLong("de.fhkl.thatsstroke.VideoProgress");

				mAdapter.setProgressOf(id, progress);
			}
			else if (action.equals(Broadcast.NEW_VIDEO))
			{
				Video video = (Video) extras.get("de.fhkl.thatsstroke.NewVideo");

				mAdapter.addVideo(video, 0);
			}
			else if (action.equals(Broadcast.TRANSMISSION_COMPLETED_ID))
			{
				UUID id = (UUID) extras.get("de.fhkl.thatsstroke.TransmissionCompletedId");

				mAdapter.videoLoaded(id);
			}
			else if (action.equals(Broadcast.VIDEO_ASSIGNED_ID))
			{
				UUID id = (UUID) extras.get("de.fhkl.thatsstroke.VideoAssignedId");

				mAdapter.remove(id);
			}
			else if (action.equals(Broadcast.ACCEPT_RATING_ID))
			{
				UUID id = (UUID) extras.get("de.fhkl.thatsstroke.AcceptRatingId");

				mAdapter.remove(id);
			}
			else if (action.equals(Broadcast.RECORDING_STARTS))
			{
				mRecordingStartsText.setVisibility(View.VISIBLE);
				Animation fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f);
				fadeOutAnimation.setDuration(7500);
				mRecordingStartsText.setAnimation(fadeOutAnimation);
				mRecordingStartsText.setOnClickListener(new OnClickListener()
				{
					@Override
					public void onClick(View v)
					{
						mRecordingStartsText.clearAnimation();
						mRecordingStartsText.setVisibility(View.GONE);
					}
				});
			}
			else if(action.equals(Broadcast.UPDATE_VIEW))
			{
				mAdapter.setList(mDataSource.getAllVideos());
				
				Toast.makeText(Inbox.this, R.string.sync_videos, Toast.LENGTH_LONG).show();
			}
		}
	}

	/**
	 * Adapter to populate the ListView with video information received from the
	 * server or stored in the database.
	 */
	public class StrokeItemAdapter extends BaseAdapter implements OnItemClickListener
	{
		private final LayoutInflater mInflater;

		/** List of the videos currently shown */
		private List<Video> mVideoList;

		public StrokeItemAdapter()
		{
			mInflater = (LayoutInflater) Inbox.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			mVideoList = new ArrayList<Video>();
		}

		@Override
		public int getCount()
		{
			return mVideoList.size();
		}

		@Override
		public Video getItem(int location)
		{
			return mVideoList.get(location);
		}

		@Override
		public long getItemId(int location)
		{
			return getItem(location).getId();
		}

		// This is the ONLY part where the ListViews' items should be changed!
		@Override
		public View getView(int position, View convertView, ViewGroup parent)
		{
			ItemView itemView;

			// Reuse old view if it's of the appropriate type and not null
			if (convertView != null && convertView.getClass().equals(ItemView.class))
			{
				itemView = (ItemView) convertView;
				itemView.setAdapter(mAdapter);
			}
			// Create new itemView from XML
			else
			{
				itemView = (ItemView) mInflater.inflate(R.layout.video_item, parent, false);
				itemView.setAdapter(mAdapter);
			}

			// Get UI elements contained by itemView
			TextView dateTextView = (TextView) itemView.findViewById(R.id.video_date_text_view);
			TextView videoStateTextView = (TextView) itemView.findViewById(R.id.video_state_text);
			ProgressBar videoProgressBar = (ProgressBar) itemView.findViewById(R.id.video_progress_bar);

			// Get data to view on the current item of the ListView
			Video video = getItem(position);

			// Always show the date of the video
			Date date = new Date(video.getDate());
			String timeString = DateFormat.getTimeInstance().format(date);
			String dateString = DateFormat.getDateInstance().format(date);
			dateTextView.setText(timeString + "\n" + dateString);

			// Update the rest of this itemView according to the current
			// state(flag) of the video
			switch ((int) video.getFlag())
			{
				case (LOADING):
					// Sets the max value of the progress bar to the nummber of
					// parts this video consists of
					if (video.getParts() != videoProgressBar.getMax())
					{
						videoProgressBar.setMax((int) video.getParts());
					}

					videoStateTextView.setText(R.string.loading);
					videoProgressBar.setVisibility(View.VISIBLE);
					videoProgressBar.setProgress((int) video.getProgress());
				break;

				case (VIEWED):
					videoStateTextView.setText(R.string.played);
					videoProgressBar.setVisibility(View.INVISIBLE);
				break;

				case (NOT_LOADED):
					videoStateTextView.setText(R.string.accept);
					videoProgressBar.setVisibility(View.INVISIBLE);
				break;

				case (LOADED):
					videoStateTextView.setText(R.string.play);
					videoProgressBar.setVisibility(View.INVISIBLE);
				break;

				case (DELETING):
					videoStateTextView.setText(R.string.deleting);
					videoProgressBar.setVisibility(View.INVISIBLE);

					Animation fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f);
					fadeOutAnimation.setDuration(2000);
					itemView.startAnimation(fadeOutAnimation, UUID.fromString(video.getVideoId()));

					video.setFlag(FADE_OUT);
				break;

				case (FADE_OUT):
				// video is getting deleted and faded out, do nothing!
				break;
			}
			return itemView;
		}

		@Override
		public void onItemClick(AdapterView<?> parent, View view, int position, long id)
		{
			Video video = getItem(position);
			UUID videoId = UUID.fromString(video.getVideoId());

			switch ((int) video.getFlag())
			{
				case (NOT_LOADED):
					// Updates the GUI
					video.setFlag(LOADING);
					video.setProgress(0);
					notifyDataSetChanged();

					mService.acceptVideo(videoId, true);
				break;

				case (LOADED):
					mService.videoStarts(videoId);
					video.setFlag(VIEWED);
					notifyDataSetChanged();

					// Intentional fallthrough

				case (VIEWED):
					Intent intent = new Intent(getApplicationContext(), VideoPlayer.class);
					intent.putExtra("de.fhkl.thatsstroke.VideoId", videoId);
					startActivityForResult(intent, 0);
				break;
			}
		}

		/**
		 * Set the list of videos to display.
		 * 
		 * @param list
		 */
		public void setList(List<Video> list)
		{

			if (list != null)
			{
				mVideoList = list;
			}
			else
			{
				mVideoList.clear();
			}

			notifyDataSetChanged();
		}

		/**
		 * Adds a single video to display at the specified position.
		 * 
		 * @param v
		 * @param position
		 */
		public void addVideo(Video v, int position)
		{
			mVideoList.add(position, v);
			notifyDataSetChanged();
		}

		/**
		 * Marks the video with the given videoId to be removed, so the view can
		 * display an animation and finally delete it by calling
		 * removeFinished()
		 * 
		 * @param videoId
		 */
		public void remove(UUID videoId)
		{
			Video video = getVideoById(videoId);
			video.setFlag(DELETING);
			notifyDataSetChanged();
		}

		/**
		 * Removes the video with the given videoId from the list of displayed
		 * videos. <br>
		 * <br>
		 * <strong>Note:<strong> Is used internally, but not in private context.
		 * To delete a video use remove(UUID) instead.
		 * 
		 * @param videoId
		 */
		public void removeFinished(UUID videoId)
		{
			mVideoList.remove(getVideoById(videoId));
			notifyDataSetChanged();
		}

		/**
		 * Changes the progress of a the video with the given videoId to the
		 * progress specified.
		 * 
		 * @param videoId
		 * @param progress
		 */
		public void setProgressOf(UUID videoId, int progress)
		{
			Video video = getVideoById(videoId);
			video.setProgress(progress);
			notifyDataSetChanged();
		}

		/**
		 * Marks the video with the given videoId as loaded to update its state
		 * on the listView.
		 * 
		 * @param videoId
		 */
		public void videoLoaded(UUID videoId)
		{
			Video video = getVideoById(videoId);
			video.setFlag(LOADED);
			notifyDataSetChanged();
		}

		/**
		 * Returns the video with the given UUID.
		 * 
		 * @param videoId
		 * @return
		 */
		private Video getVideoById(UUID videoId)
		{
			String s = videoId.toString();

			for (Video v : mVideoList)
			{
				if (v.getVideoId().equals(s))
				{
					return v;
				}
			}
			return null;
		}
	}
}
